import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ElementRef,
  EventEmitter,
  Input,
  OnDestroy,
  OnInit,
  Output,
  Renderer2,
  TemplateRef,
  TrackByFunction,
  ViewChild,
  ViewContainerRef,
  ViewEncapsulation,
} from '@angular/core';
import { merge, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { AnimationEvent } from '@angular/animations';
import { TemplatePortal } from '@angular/cdk/portal';
import { Overlay, OverlayConfig, PositionStrategy } from '@angular/cdk/overlay';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { InputBoolean, InputNumber, LvSizeLD, LvSizeLSD, OverlayService, showItem } from '../core/api';
import { TypeUtils } from '../core/utils/api';
import { LvConfig } from '../core/config';
import { ADJUST_POSITION_MAP, getPosition, POSITION_MAP } from './autocomplete-position';
import { AutocompleteItem } from './interface';
@Component({
  selector: 'lv-autocomplete',
  exportAs: 'lvAutocomplete',
  templateUrl: './autocomplete.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  host: {
    '[class.lv-autocomplete-host]': 'true',
  },
  animations: [showItem],
})
export class AutocompleteComponent implements OnInit, OnDestroy {
  @Input() lvPanelClass: string;

  @Input() lvWidth: string | number;
  @Input() lvLoading: boolean = false;

  @Input() lvHighlightContent: string;
  @Input() lvHeader: TemplateRef<void>;
  @Input() lvShowNoData: boolean = true;
  @Input() lvSize: LvSizeLD = 'default';
  @Input() lvTrackBy: TrackByFunction<any>; // 内容列表trackBy
  @Input() @InputNumber() lvVirtualItemSize; // 每行高度
  @Input() @InputNumber() lvVirtualMaxBuffer; // 渲染新项时最大缓冲高度
  @Input() @InputNumber() lvVirtualMinBuffer; // 视图之外渲染的最小缓冲区数，若缓存区数量低于此数量将渲染新项
  @Input() lvLabelTemplate: TemplateRef<void>;
  @Input() lvNoDataTemplate: TemplateRef<void>;
  @Output() lvScrollEnd = new EventEmitter<any>(); // 虚拟滚动到底部时回调事件
  @Input() @InputBoolean() lvVirtualScroll = false; // 是否使用虚拟滚动（大数据场景使用）

  @Input() lvData: string[] | AutocompleteItem[] = [];
  @Output() lvScrollingChange = new EventEmitter<any>(); // 虚拟滚动中回调事件

  @Output() lvShow: EventEmitter<any> = new EventEmitter();
  @Output() lvHide: EventEmitter<any> = new EventEmitter();
  @Input() lvScroll: { x?: string | null; y?: string | null }; // 下拉选项水平垂直高度，超过滚动 目前只有y
  @ViewChild('scrollComponent', { static: false }) _scrollViewport: CdkVirtualScrollViewport;
  @ViewChild('autocompleteTemplate', { static: true }) autocompleteTemplate: TemplateRef<any>;
  @Input() lvPosition: 'topLeft' | 'topCenter' | 'topRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight' =
    'bottomLeft';

  public target; // overlay origin element
  public _overlay;
  scrollY: string;
  public panelWidth;
  virtualScrollY: string;
  public active: string = '';
  public isPanelOpen = false;
  public selected: string = '';
  public typeUtils = TypeUtils;
  public inputValue: string = '';

  public _animationState = 'void';
  public valueChange$ = new Subject<any>();
  public dropopen$ = new Subject<boolean>();
  public assetsPrefixPath = LvConfig.assetsPrefixPath;

  private _direction;
  private destroy$ = new Subject<void>();
  private openState$ = new Subject<boolean>();

  private defaultVirtualItemSizeConfig = {
    large: 40,
    default: 32,
    small: 24,
  };

  constructor(
    private el: ElementRef,
    private renderer: Renderer2,
    public cdr: ChangeDetectorRef,
    private viewContainerRef: ViewContainerRef,
    private overlay: Overlay,
    private overlayService: OverlayService,
  ) {}

  itemClick(event, item): void {
    this.valueChange$.next(item);
  }

  genScrollY(): void {
    this.scrollY = this.getScrollY(this.lvScroll, this.lvSize);
  }

  close(): void {
    if (this._overlay?.overlayRef.hasAttached()) {
      this._overlay.close();
    }
  }

  genVirtualScrollY(): void {
    this.virtualScrollY = this.getVirtualScrollY();
    setTimeout((): void => {
      this._scrollViewport?.checkViewportSize();
    });
  }

  ngOnDestroy(): void {
    if (this._overlay?.overlayRef.hasAttached()) {
      this._overlay.overlayRef.dispose();
    }

    this.destroy$.next();
    this.destroy$.complete();
  }

  beforeClose(): void {
    this.openState$.next(true);

    if (this._direction === 'up') {
      this._animationState = 'hideUp';
    } else {
      this._animationState = 'hideDown';
    }
  }

  onAnimationDone(event: AnimationEvent): void {
    const isAnimationDone =
      (event.fromState === 'showUp' && event.toState === 'hideUp') ||
      (event.fromState === 'showDown' && event.toState === 'hideDown');
    if (isAnimationDone) {
      this.openState$.next(false);
    }
  }

  scrolledIndexChange(e: number): void {
    this.cdr.markForCheck();
    const view = this._scrollViewport;
    if (!view) {
      return;
    }
    const total = view.getDataLength();
    const size = view.getViewportSize();

    if (!total) {
      return;
    }

    this.lvScrollingChange.emit({ source: view, index: e });
    if (total - Math.floor(size / this.lvVirtualItemSize) === e) {
      this.lvScrollEnd.emit();
    }
  }

  ngOnInit(): void {
    this.lvVirtualItemSize = this.lvVirtualItemSize || this.defaultVirtualItemSizeConfig[this.lvSize];
    this.dropopen$.pipe(takeUntil(this.destroy$)).subscribe((open): void => {
      if (open) {
        this.open();
      } else {
        this.close();
      }
    });

    merge(this.dropopen$, this.openState$)
      .pipe(takeUntil(this.destroy$))
      .subscribe((): void => {
        this.cdr.detectChanges();
      });
  }

  setSelectedItemPosition(): void {
    const panelHost = this._overlay.overlayRef.hostElement;
    const container = panelHost.querySelector('.lv-autocomplete-content-list');
    const el = panelHost.querySelector('.lv-autocomplete-item-active');
    if (!el) {
      return;
    }

    const offsetTop = el.offsetTop - container.clientTop;
    const { scrollTop } = container;
    if (offsetTop < scrollTop) {
      container.scrollTop = offsetTop;
    }
    if (offsetTop > scrollTop + container.clientHeight - el.offsetHeight) {
      container.scrollTop = offsetTop - container.clientHeight + el.offsetHeight;
    }
  }

  afterOpen(overlayInstance): void {
    // 设置z-index
    this.renderer.addClass(this._overlay.overlayRef.hostElement, 'lv-autocomplete-z-index');

    // 打开后的回调函数
    this.lvShow.emit();

    // 获取打开的方向
    overlayInstance.overlayRef._positionStrategy.positionChanges.subscribe((e): void => {
      const _position = getPosition(e.connectionPair);
      if (_position === 'topLeft' || _position === 'topCenter' || _position === 'topRight') {
        this._animationState = 'showUp';
        this._direction = 'up';
        this.cdr.markForCheck();
      } else {
        this._animationState = 'showDown';
        this._direction = 'down';
        this.cdr.markForCheck();
      }
    });
  }

  updatePosition(event): void {
    const _disX =
      document.documentElement.clientWidth - event.clientX - this._overlay.overlayRef.overlayElement.clientWidth;
    const _disY =
      document.documentElement.clientHeight - event.clientY - this._overlay.overlayRef.overlayElement.clientHeight;
    const _top = _disY < 0 ? event.clientY - this._overlay.overlayRef.overlayElement.clientHeight : event.clientY;
    const _left = _disX < 0 ? event.clientX - this._overlay.overlayRef.overlayElement.clientWidth : event.clientX;

    if (_disX < 0 || _disY < 0) {
      const strategy: PositionStrategy = this.overlay.position().global().left(`${_left}px`).top(`${_top}px`);
      this._overlay.overlayRef.updatePositionStrategy(strategy);
    }
  }

  open(): void {
    if (
      this._overlay?.overlayRef.hasAttached() ||
      !this.overlayService.getTargetNode(document.activeElement, this.target.nativeElement)
    ) {
      return;
    }

    const config = new OverlayConfig({
      hasBackdrop: false,
      width: this.lvWidth ? this.lvWidth : this.panelWidth,
      positionStrategy: this.overlay
        .position()
        .flexibleConnectedTo(this.target)
        .withPositions([POSITION_MAP[this.lvPosition], ...ADJUST_POSITION_MAP]),
      scrollStrategy: this.overlay.scrollStrategies.block(),
    });

    const extendConfig = {
      afterOpen: (overlayRef): void => {
        this._overlay = overlayRef;
        this.afterOpen(overlayRef);
        this.isPanelOpen = true;
      },
      beforeClose: (): Promise<any> =>
        new Promise((resolve): void => {
          this.beforeClose();

          const closesubscribe = this.openState$.subscribe((open): void => {
            if (!open) {
              closesubscribe.unsubscribe();
              resolve(true);
            }
          });
        }),
      afterClose: (): void => {
        this.lvHide.emit();
        this.dropopen$.next(false);
        this.isPanelOpen = false;
      },
    };

    const portal = new TemplatePortal(this.autocompleteTemplate, this.viewContainerRef);

    this.overlayService.open(portal, config, extendConfig);
  }

  private getVirtualScrollY(): string {
    return `${Math.min(this.lvVirtualItemSize * this.lvData.length, parseFloat(this.scrollY))}px`;
  }

  private getScrollY(lvScroll: { x?: string | null; y?: string | null }, lvSize: LvSizeLSD): string {
    const max = 9;
    const scrollY = lvScroll?.y;
    return scrollY ? scrollY : `${this.defaultVirtualItemSizeConfig[lvSize] * max}px`;
  }
}
